package com.kancy.okhttp.client.config;

import com.kancy.okhttp.client.properties.OkHttpClientProperties;
import com.kancy.okhttp.client.service.HttpClient;
import com.kancy.okhttp.client.service.impl.OkHttpClientImpl;
import com.kancy.okhttp.client.support.HttpClientLogInterceptor;
import com.kancy.okhttp.client.support.OkHttpClientDynamicTimeoutInterceptor;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.okhttp3.OkHttpMetricsEventListener;
import io.micrometer.core.ipc.http.HttpSender;
import io.micrometer.core.ipc.http.OkHttpSender;
import okhttp3.ConnectionPool;
import okhttp3.EventListener;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * OkHttpClientAutoConfiguration
 *
 * @author kancy
 * @date 2020/5/1 12:27
 */
@Import({OkHttpClientAutoConfiguration.OkHttpEventListenerConfig.class,
        OkHttpClientAutoConfiguration.HttpSenderConfig.class,
        HttpClientLogInterceptor.class})
public class OkHttpClientAutoConfiguration {

    private static final Logger log = LoggerFactory.getLogger(OkHttpClientAutoConfiguration.class);

    @Bean
    @ConfigurationProperties("okhttp.client")
    public OkHttpClientProperties okHttpClientProperties(){
        return new OkHttpClientProperties();
    }

    @Bean
    public HttpClient httpClient(@Qualifier("restTemplate") RestTemplate restTemplate){
        return new OkHttpClientImpl(restTemplate);
    }

    @Bean
    @Primary
    public RestTemplate restTemplate(@Qualifier("okHttp3ClientHttpRequestFactory") ClientHttpRequestFactory requestFactory,
                                    HttpClientLogInterceptor httpClientLogInterceptor,
                                     OkHttpClientProperties properties) {
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        // 添加拦截器
        if (Objects.nonNull(httpClientLogInterceptor) && properties.isLogInterceptorEnabled()){
            restTemplate.getInterceptors().add(httpClientLogInterceptor);
        }
        // 修改消息转换器
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        messageConverters.remove(1);
        StringHttpMessageConverter messageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        messageConverter.setWriteAcceptCharset(false);
        messageConverters.add(1, messageConverter);
        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory okHttp3ClientHttpRequestFactory(@Qualifier("okHttpClient") OkHttpClient okHttpClient) {
        return new OkHttp3ClientHttpRequestFactory(okHttpClient);
    }

    @Bean
    public OkHttpClient okHttpClient(@Qualifier("okHttp3ClientConnectionPool") ConnectionPool connectionPool,
                                     @Qualifier("okHttp3ClientEventListener") EventListener eventListener,
                                     OkHttpClientProperties properties) {
        OkHttpClient okHttpClient = new OkHttpClient().newBuilder().connectionPool(connectionPool)
                .addInterceptor(new OkHttpClientDynamicTimeoutInterceptor())
                .connectTimeout(properties.getConnectTimeout().toMillis(), TimeUnit.MILLISECONDS)
                .readTimeout(properties.getReadTimeout().toMillis(), TimeUnit.MILLISECONDS)
                .writeTimeout(properties.getWriteTimeout().toMillis(), TimeUnit.MILLISECONDS)
                .eventListener(eventListener)
                .build();
        addShutdownHook(okHttpClient);
        return okHttpClient;
    }

    /**
     * 连接池
     * @param properties
     * @return
     */
    @Bean
    public ConnectionPool okHttp3ClientConnectionPool(OkHttpClientProperties properties) {
        return new ConnectionPool(properties.getMaxIdleConnections(),
                properties.getKeepAlive().toMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 默认的事件监听器
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public EventListener okHttp3ClientEventListener(){
        return new EventListener(){};
    }


    @ConditionalOnClass({HttpSender.class})
    class HttpSenderConfig{
        @Bean
        public HttpSender okHttpSender(@Qualifier("okHttpClient") OkHttpClient okHttpClient) {
            return new OkHttpSender(okHttpClient);
        }
    }

    @ConditionalOnClass({OkHttpMetricsEventListener.class, MeterRegistry.class, MetricsProperties.class})
    class OkHttpEventListenerConfig {
        /**
         * 事件监听器 OkHttpMetricsEventListener
         * @return
         */
        @Bean
        public EventListener okHttp3ClientEventListener(MeterRegistry meterRegistry, MetricsProperties metricsProperties){
            return OkHttpMetricsEventListener
                    .builder(meterRegistry, metricsProperties.getWeb().getClient().getRequestsMetricName())
                    // 解决FeignClient无法找到url问题
                    .uriMapper(request -> request.url().encodedPath())
                    .build();
        }
    }


    /**
     * 线程池关闭的钩子
     * @param okHttpClient
     */
    private void addShutdownHook(OkHttpClient okHttpClient) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            ConnectionPool connectionPool = okHttpClient.connectionPool();
            connectionPool.evictAll();
            log.info("OkHttp client connections idle: {}, all: {}", connectionPool.idleConnectionCount(), connectionPool.connectionCount());
            ExecutorService executorService = okHttpClient.dispatcher().executorService();
            executorService.shutdown();
            try {
                executorService.awaitTermination(3, TimeUnit.MINUTES);
                log.info("OkHttp Client ExecutorService closed.");
            } catch (InterruptedException e) {
                log.warn("InterruptedException on destroy()", e);
                Thread.currentThread().interrupt();
            }
        }, "OkHttpClientShutDownHook"));
    }
}
